﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Look_Through_Windows.Helper;
using Look_Through_Windows.Network;

namespace Look_Through_Windows.Logic
{
    /// <summary>
    /// Hub class for windowing stuff
    /// </summary>
    class WindowHub
    {
        /// <summary>
        /// Set of sharing windows (to peers)
        /// </summary>
        public Dictionary<IntPtr, SharedWindow> SharedWindows = new Dictionary<IntPtr, SharedWindow>( );

        /// <summary>
        /// Set of connections
        /// </summary>
        public HashSet<NetConnection> Connections = new HashSet<NetConnection>( );

        /// <summary>
        /// Possible shared windows
        /// </summary>
        public Dictionary<NetConnection, Dictionary<IntPtr, RemoteWindow>> PossibleRemoteWindows = new Dictionary<NetConnection, Dictionary<IntPtr, RemoteWindow>>( );

        /// <summary>
        /// Netserver
        /// </summary>
        private NetServer netServer;

        /// <summary>
        /// Windows hub
        /// </summary>
        public WindowHub( int port )
        {
            netServer = new NetServer( port );
            netServer.ConnectionAccept += AddConnection;
            netServer.ConnectionClose += RemoveConnection;
        }

        /// <summary>
        /// Updates
        /// </summary>
        public void Update( )
        {
            netServer.CheckMessages( );
            foreach ( var connection in Connections )
                connection.CheckMessages( );

            Queue<SharedWindow> invalidWins = new Queue<SharedWindow>( );
            foreach ( var sharedWindow in SharedWindows.Values )
            {
                if ( !sharedWindow.Valid )
                    invalidWins.Enqueue( sharedWindow );
                else sharedWindow.Update( );
            }

            foreach ( var invalidWin in invalidWins )
            {
                invalidWin.StopSharing( );
                SharedWindows.Remove( invalidWin.Hwnd );
            }
            if ( invalidWins.Count > 0 )
            {
                foreach (var conn in Connections)
                    SendSharingList( conn );
                if ( OnSharedListUpdate != null )
                    OnSharedListUpdate( );
            }
        }

        /// <summary>
        /// Adds a sharing window
        /// </summary>
        public void AddSharingWindow( IntPtr hwnd )
        {
            SharedWindows.Add( hwnd, new SharedWindow( hwnd ) );
            foreach ( var connection in Connections )
                SendSharingList( connection );
        }

        /// <summary>
        /// Connects to
        /// </summary>
        public void ConnectTo( string host, int port )
        {
            var conn = NetConnection.ConnectTo( host, port, ThreadPriority.Normal );
            conn.ConnectionClose += RemoveConnection;
            AddConnection( conn );
        }

        private void RemoveConnection( NetConnection conn )
        {
            foreach ( var sharedWindow in SharedWindows.Values )
                if ( sharedWindow.Peers.Contains( conn ) )
                    sharedWindow.RemovePeer( conn );

            Connections.Remove( conn );
            Notifier.Instance.AddNotification( "Connection closed: " + conn + ( conn.Tag == null ? "" : " (" + conn.Tag + ")" ) );
        }

        private void AddConnection( NetConnection conn )
        {
            conn.MessageRecieve += conn_MessageRecieve;
            SendUserName( conn );
            SendSharingList( conn );
            Connections.Add( conn );

            Notifier.Instance.AddNotification( "New Connection: " + conn );
        }

        /// <summary>
        /// On remote list update
        /// </summary>
        public event Action OnRemoteListUpdate;
        /// <summary>
        /// On shared list update
        /// </summary>
        public event Action OnSharedListUpdate;

        void conn_MessageRecieve( NetMessage msg, NetConnection conn )
        {
            byte code = msg.ReadByte( "Code" );
            switch ( code )
            {
                case SendCodes.KeepAlive:
                    break;

                case SendCodes.SharingListReq:
                    SendSharingList( conn );
                    break;

                case SendCodes.UserName:
                    conn.Tag = msg.ReadString( "UserName" );
                    Notifier.Instance.AddNotification( "Connection " + conn + "\nis now known as " + conn.Tag );
                    break;

                case SendCodes.WindowContent:
                    {
                        IntPtr hwnd = new IntPtr( msg.ReadLong( "hwnd" ) );
                        if ( PossibleRemoteWindows.ContainsKey( conn ) && PossibleRemoteWindows[ conn ].ContainsKey( hwnd ) )
                        {
                            var win = PossibleRemoteWindows[ conn ][ hwnd ];
                            win.UpdateBitmap( msg );
                            NetMessage ackmsg = new NetMessage( SendCodes.WindowContentAck );
                            ackmsg.Write( hwnd.ToInt64( ), "hwnd" );
                            conn.Send( ackmsg );
                        }
                    }
                    break;
                case SendCodes.WindowContentAck:
                    {
                        IntPtr hwnd = new IntPtr( msg.ReadLong( "hwnd" ) );
                        if ( SharedWindows.ContainsKey( hwnd ) )
                        {
                            var win = SharedWindows[ hwnd ];
                            win.Ack( conn );
                        }
                    }
                    break;

                case SendCodes.ShareWindowReq:
                    {
                        IntPtr hwnd = new IntPtr( msg.ReadLong( "hwnd" ) );
                        if ( SharedWindows.ContainsKey( hwnd ) )
                        {
                            var win = SharedWindows[ hwnd ];
                            win.AddPeer( conn );
                            Notifier.Instance.AddNotification( conn.Tag + " (" + conn + ") now views" + "\nWindow " + hwnd.ToInt64( ).ToString( "X" ) + " \"" + win.Name + "\"" );
                        }
                    }
                    break;
                case SendCodes.ShareWindowStop:
                    {
                        IntPtr hwnd = new IntPtr( msg.ReadLong( "hwnd" ) );
                        if ( SharedWindows.ContainsKey( hwnd ) )
                        {
                            var win = SharedWindows[ hwnd ];
                            win.RemovePeer( conn );
                            Notifier.Instance.AddNotification( conn.Tag + " (" + conn + ") stopped viewing of" + "\nWindow " + hwnd.ToInt64( ).ToString( "X" ) + " \"" + win.Name + "\"" );
                        }
                    }
                    break;
                case SendCodes.ShareWindowStopSharer:
                    {
                        IntPtr hwnd = new IntPtr( msg.ReadLong( "hwnd" ) );
                        if ( PossibleRemoteWindows.ContainsKey( conn ) && PossibleRemoteWindows[ conn ].ContainsKey( hwnd ) )
                        {
                            var win = PossibleRemoteWindows[ conn ][ hwnd ];
                            win.CancelShared( );
                            //Notifier.Instance.AddNotification( conn.Tag + " (" + conn + ") forced cancellation of" + "\nWindow " + hwnd.ToInt64( ).ToString( "X" ) + " \"" + win.Name + "\"" );
                        }
                    }
                    break;

                case SendCodes.SharingList:
                    {
                        int count = msg.ReadInt( "count" );
                        Dictionary<IntPtr, RemoteWindow> wSet;
                        if ( PossibleRemoteWindows.ContainsKey( conn ) ) wSet = PossibleRemoteWindows[ conn ];
                        else wSet = PossibleRemoteWindows[ conn ] = new Dictionary<IntPtr, RemoteWindow>( );
                        HashSet<IntPtr> deletedHwnds = new HashSet<IntPtr>( wSet.Keys );
                        for ( int i = 0 ; i < count ; ++i )
                        {
                            IntPtr hwnd = new IntPtr( msg.ReadLong( "hwnd" ) );
                            string name = msg.ReadString( "name" );
                            deletedHwnds.Remove( hwnd );

                            bool notify = !wSet.ContainsKey( hwnd );

                            RemoteWindow win = wSet.ContainsKey( hwnd ) ? wSet[ hwnd ] : wSet[ hwnd ] = new RemoteWindow( );
                            win.Name = name;
                            win.Hwnd = hwnd;
                            win.Sharer = conn;

                            if ( notify )
                                Notifier.Instance.AddNotification( conn.Tag + " (" + conn + ") shared" + "\nWindow " + hwnd.ToInt64( ).ToString( "X" ) + " \"" + name + "\"\n" + "(Double-Click to View)",
                                    win.RequestSharing );
                        }
                        foreach ( var deletedHwnd in deletedHwnds )
                        {
                            var win = wSet[ deletedHwnd ];
                            var hwnd = win.Hwnd;
                            var name = win.Name;
                            Notifier.Instance.AddNotification( conn.Tag + " (" + conn + ") stopped sharing of" + "\nWindow " + hwnd.ToInt64( ).ToString( "X" ) + " \"" + name + "\"" );
                            win.CancelShared( );
                            wSet.Remove( deletedHwnd );
                        }
                        if ( OnRemoteListUpdate != null )
                            OnRemoteListUpdate( );
                    }
                    break;

                default:
                    throw new Exception( "Unrecognized code 0x" + code.ToString( "X" ) );
            }

            if ( !msg.EndOfMessage ) throw new FormatException( "Not read to end" );
        }

        /// <summary>
        /// Sends a list of sharing windows
        /// </summary>
        private void SendSharingList( NetConnection conn )
        {
            NetMessage msg = new NetMessage( SendCodes.SharingList );
            msg.Write( SharedWindows.Count, "count" );
            foreach ( var window in SharedWindows.Values )
            {
                msg.Write( window.Hwnd.ToInt64( ), "hwnd" );
                msg.Write( window.Name, "name" );
            }
            conn.Send( msg );
        }

        /// <summary>
        /// Sends the user name
        /// </summary>
        private void SendUserName( NetConnection conn )
        {
            NetMessage msg = new NetMessage( SendCodes.UserName );
            msg.Write( Environment.UserName, "UserName" );
            conn.Send( msg );
        }
    }
}
